<script lang="tsx">
import { propTypes } from '@/utils/propTypes';
import type { PropType } from 'vue';
import { computed, defineComponent, h, unref } from 'vue';

export default defineComponent({
  name: 'Highlight',
  props: {
    tag: propTypes.string.def('span'),
    keys: {
      type: Array as PropType<string[]>,
      default: () => []
    },
    color: propTypes.string.def('var(--el-color-primary)')
  },
  emits: ['click'],
  setup(props, { emit, slots }) {
    const highlightRegex = computed(() => {
      // 过滤空值并去重
      const validKeys = [...new Set(props.keys.filter((key) => key.trim().length > 0))];

      if (validKeys.length === 0) return null;

      // 转义特殊字符并生成正则
      const escapedKeys = validKeys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
      return {
        regex: new RegExp(`(${escapedKeys.join('|')})`, 'g'),
        keys: validKeys
      };
    });

    // 高亮节点创建
    const createKeyNode = (key: string) =>
      h(
        'span',
        {
          onClick: () => emit('click', key),
          style: {
            color: props.color,
            cursor: 'pointer'
          }
        },
        key
      );

    const parseText = (text: string) => {
      const regexData = unref(highlightRegex);
      if (!regexData) return [text];

      const segments: (string | ReturnType<typeof h>)[] = [];
      let lastIndex = 0;

      // 使用 matchAll 一次性获取所有匹配
      const matches = Array.from(text.matchAll(regexData.regex));

      for (const match of matches) {
        // 添加匹配前的普通文本
        if (match.index > lastIndex) {
          segments.push(text.slice(lastIndex, match.index));
        }

        // 添加高亮节点
        segments.push(createKeyNode(match[0]));
        lastIndex = match.index + match[0].length;
      }

      // 添加剩余文本
      if (lastIndex < text.length) {
        segments.push(text.slice(lastIndex));
      }

      return segments;
    };

    const getTextContent = (nodes: any): string | null => {
      return nodes?.[0]?.children && typeof nodes[0].children === 'string' ? nodes[0].children : null;
    };
    return () => {
      const defaultSlot = slots?.default?.();
      const textContent = getTextContent(defaultSlot);

      if (!textContent) return defaultSlot;

      const nodes = parseText(textContent);
      return nodes.length === 1 && typeof nodes[0] === 'string' ? h(props.tag, nodes[0]) : h(props.tag, nodes);
    };
  }
});
</script>
